package pl.elka.pobr.ppr.logic.recognition;

import java.util.Iterator;
import java.util.Vector;

import pl.elka.pobr.ppr.logic.Coord;
import pl.elka.pobr.ppr.utils.Log;

public class MomentsComputer {

    private static final String LOG_TAG = "MomentsComputer";
    private Vector<Segment> mSegments;
    private boolean[][] mBitmapPx;
    private int mW;
    private int mH;

    public MomentsComputer(Vector<Segment> segments, boolean[][] bitmapPixels) {
        mSegments = segments;
        mW = bitmapPixels.length;
        mH = bitmapPixels[0].length;
        mBitmapPx = bitmapPixels;
    }

    public void computeMoments() {
        for (Iterator<Segment> iterator = mSegments.iterator(); iterator.hasNext();) {
            Segment seg = iterator.next();
            computeMoments(seg);
        }
    }

    private void computeMoments(Segment seg) {
        Vector<Coord> coords = seg.getPixels();
        Vector<MomentsStorage> moments = new Vector<MomentsStorage>();
        int S = coords.size(); // figure area size
        int L = 0; // perimeter of figure
        double m00 = 0;
        double m10 = 0;
        double m01 = 0;
        double m11 = 0;
        double m20 = 0;
        double m02 = 0;
        double m12 = 0;
        double m21 = 0;
        double m30 = 0;
        double m03 = 0;
        int[] minAndMaxVals = findMinAndMax(coords);
        int x0 = minAndMaxVals[0];
        int x1 = minAndMaxVals[1];
        int y0 = minAndMaxVals[2];
        int y1 = minAndMaxVals[3];
        int geomCenterX = (x0 + x1) / 2;
        int geomCenterY = (y0 + y1) / 2;
        for (Iterator<Coord> iterator = coords.iterator(); iterator.hasNext();) {
            Coord c = iterator.next();
            int x = c.x;
            int y = c.y;
            m00 += 1;
            m10 += x;
            m01 += y;
            m11 += x * y;
            m20 += x * x;
            m02 += y * y;
            m12 += x * y * y;
            m21 += x * x * y;
            m30 += x * x * x;
            m03 += y * y * y;
            // if at least one pixel is different then current (so different true) we are the edge pixel so increase perimeter
            if (x > 0 && x < mBitmapPx.length - 1 && y > 0 && y < mBitmapPx[0].length - 1) {
                if (!mBitmapPx[x - 1][y] || !mBitmapPx[x + 1][y] || !mBitmapPx[x][y - 1] || !mBitmapPx[x][y + 1]) {
                    L += 1;
                }
            }
        }
        int weightCenterX = (int) (m10 / m00);
        int weightCenterY = (int) (m01 / m00);
        double rMin = mW > mH ? mW : mH; // set it as max size of the image
        double rMax = 1; // set it as minimal distance
        for (Iterator<Coord> iterator = coords.iterator(); iterator.hasNext();) {
            Coord c = iterator.next();
            int x = c.x;
            int y = c.y;
            // if at least one pixel is different then current (so different true) we are the edge pixel so count rMin and rMax
            if (x > 0 && x < mBitmapPx.length - 1 && y > 0 && y < mBitmapPx[0].length - 1) {
                if (!mBitmapPx[x - 1][y] || !mBitmapPx[x + 1][y] || !mBitmapPx[x][y - 1] || !mBitmapPx[x][y + 1]) {
                    // count rMin and rMax
                    double dist = Math.sqrt(Math.pow(weightCenterX - x, 2) + Math.pow(weightCenterY - y, 2));
                    if (dist < rMin) {
                        rMin = dist;
                    }
                    if (dist > rMax) {
                        rMax = dist;
                    }
                }
            }
        }
        int lMax = x1 - x0 > y1 - y0 ? x1 - x0 : y1 - y0; // max size
        // count moments
        double picI = m10 / m00;
        double picJ = m01 / m00;
        // shape moments
        Log.d(LOG_TAG, "rMin=" + rMin);
        Log.d(LOG_TAG, "rMax=" + rMax);
        Log.d(LOG_TAG, "lMax=" + lMax);
        double W3 = L / (2 * Math.sqrt(Math.PI * S)) - 1;
        double W7_Lp1 = rMin / rMax;
        double W8_Lp2 = (double) lMax / (double) L;
        // geometric moments
        double M00 = m00;
        double M01 = m01 - (m01 / m00) * m00;
        double M10 = m10 - (m10 / m00) * m00;
        double M11 = m11 - m10 * m01 / m00;
        double M20 = m20 - m10 * m10 / m00;
        double M02 = m02 - m01 * m01 / m00;
        double M21 = m21 - 2 * m11 * picI - m20 * picJ + 2 * m01 * picI * picI;
        double M12 = m12 - 2 * m11 * picJ - m02 * picI + 2 * m10 * picJ * picJ;
        double M30 = m30 - 3 * m20 * picI + 2 * m10 * picI * picI;
        double M03 = m03 - 3 * m02 * picJ + 2 * m01 * picJ * picJ;
        double[] M = new double[11]; // first is not used
        M[1] = (M20 + M02) / Math.pow(m00, 2);
        M[2] = (Math.pow(M20 - M02, 2) + 4 * M11 * M11) / Math.pow(m00, 4);
        M[3] = (Math.pow(M30 - 3 * M12, 2) + Math.pow(3 * M21 - M03, 2)) / Math.pow(m00, 5);
        M[4] = (Math.pow(M30 + M12, 2) + Math.pow(M21 + M03, 2)) / Math.pow(m00, 5);
        M[5] = ((M30 - 3 * M12) * (M30 + M12) * (Math.pow(M30 + M12, 2) - 3 * Math.pow(M21 + M03, 2)) + (3 * M21 - M03)
                * (M21 + M03) * (3 * Math.pow(M30 + M12, 2) - Math.pow(M21 + M03, 2)))
                / Math.pow(m00, 10);
        M[6] = ((M20 - M02) * (Math.pow(M30 + M12, 2) - Math.pow(M21 + M03, 2)) + 4 * M11 * (M30 + M12) * (M21 + M03))
                / Math.pow(m00, 7);
        M[7] = (M20 * M02 - M11 * M11) / Math.pow(m00, 4);
        M[8] = (M30 * M12 + M21 * M03 - M12 * M12 - M21 * M21) / Math.pow(m00, 5);
        M[9] = (M20 * (M21 * M03 - M12 * M12) + M02 * (M03 * M12 - M21 * M21) - M11 * (M30 * M03 - M21 * M12)) / Math.pow(m00, 7);
        M[10] = (Math.pow(M30 * M03 - M12 * M21, 2) - 4 * (M30 * M12 - M21 * M21) * (M03 * M21 - M12)) / Math.pow(m00, 10);
        // set computed moments to the segment
        seg.setValues(new MomentsStorage(W3, W7_Lp1, W8_Lp2, M), S, L, geomCenterX, geomCenterY, weightCenterX, weightCenterY);
    }

    /**
     * @param coords
     * @return int[4] table with values: vals[0] = minX; vals[1] = maxX; vals[2] = minY; vals[3] = maxY;
     */
    public static int[] findMinAndMax(Vector<Coord> coords) {
        // this is arbitrary value that image width and height must be smaller
        int minX = 10000;
        int minY = 10000;
        int maxX = -1;
        int maxY = -1;
        for (Iterator<Coord> iterator = coords.iterator(); iterator.hasNext();) {
            Coord c = iterator.next();
            if (c.x < minX) {
                minX = c.x;
            }
            if (c.x > maxX) {
                maxX = c.x;
            }
            if (c.y < minY) {
                minY = c.y;
            }
            if (c.y > maxY) {
                maxY = c.y;
            }
        }
        int[] vals = new int[4];
        vals[0] = minX;
        vals[1] = maxX;
        vals[2] = minY;
        vals[3] = maxY;
        return vals;
    }
}
